import React from 'react'
import { useAppNavigate } from '../../navigation/use-app-navigate'
import { useQueryContext } from '../../query-context'
import Chart from 'chart.js/auto'
import GraphTooltip from './graph-tooltip'
import { buildDataSet, METRIC_LABELS, hasMultipleYears } from './graph-util'
import dateFormatter from './date-formatter'
import FadeIn from '../../fade-in'
import classNames from 'classnames'
import { hasConversionGoalFilter } from '../../util/filters'
import { MetricFormatterShort } from '../reports/metric-formatter'
import { UIMode, useTheme } from '../../theme-context'

const calculateMaximumY = function (dataset) {
  const yAxisValues = dataset
    .flatMap((item) => item.data)
    .map((item) => item || 0)

  if (yAxisValues) {
    return Math.max(...yAxisValues)
  } else {
    return 1
  }
}

class LineGraph extends React.Component {
  constructor(props) {
    super(props)
    this.regenerateChart = this.regenerateChart.bind(this)
    this.updateWindowDimensions = this.updateWindowDimensions.bind(this)
  }

  getGraphMetric() {
    let metric = this.props.graphData.metric

    if (metric == 'visitors' && hasConversionGoalFilter(this.props.query)) {
      return 'conversions'
    } else {
      return metric
    }
  }

  regenerateChart() {
    const { graphData, query, theme } = this.props
    const metric = this.getGraphMetric()
    const graphEl = document.getElementById('main-graph-canvas')
    this.ctx = graphEl.getContext('2d')
    const dataSet = buildDataSet(
      graphData.plot,
      graphData.comparison_plot,
      graphData.present_index,
      this.ctx,
      METRIC_LABELS[metric]
    )

    return new Chart(this.ctx, {
      type: 'line',
      data: {
        labels: graphData.labels,
        datasets: dataSet
      },
      options: {
        animation: false,
        plugins: {
          legend: { display: false },
          tooltip: {
            enabled: false,
            mode: 'index',
            intersect: false,
            position: 'average',
            external: GraphTooltip(graphData, metric, query, theme)
          }
        },
        responsive: true,
        maintainAspectRatio: false,
        onResize: this.updateWindowDimensions,
        elements: { line: { tension: 0 }, point: { radius: 0 } },
        onClick: this.maybeHopToHoveredPeriod.bind(this),
        scale: {
          ticks: { precision: 0, maxTicksLimit: 8 }
        },
        scales: {
          y: {
            min: 0,
            suggestedMax: calculateMaximumY(dataSet),
            ticks: {
              callback: MetricFormatterShort[metric],
              color:
                theme.mode === UIMode.dark ? 'rgb(161, 161, 170)' : undefined
            },
            grid: {
              zeroLineColor: 'transparent',
              drawBorder: false,
              color:
                theme.mode === UIMode.dark
                  ? 'rgba(39, 39, 42, 0.75)'
                  : 'rgb(236, 236, 238)'
            }
          },
          yComparison: {
            min: 0,
            suggestedMax: calculateMaximumY(dataSet),
            display: false,
            grid: { display: false }
          },
          x: {
            grid: { display: false },
            ticks: {
              callback: function (val, _index, _ticks) {
                if (this.getLabelForValue(val) == '__blank__') return ''

                const shouldShowYear = hasMultipleYears(graphData)

                if (graphData.interval === 'hour' && query.period !== 'day') {
                  const date = dateFormatter({
                    interval: 'day',
                    longForm: false,
                    period: query.period,
                    shouldShowYear
                  })(this.getLabelForValue(val))

                  const hour = dateFormatter({
                    interval: graphData.interval,
                    longForm: false,
                    period: query.period,
                    shouldShowYear
                  })(this.getLabelForValue(val))

                  // Returns a combination of date and hour. This is because
                  // small intervals like hour may return multiple days
                  // depending on the query period.
                  return `${date}, ${hour}`
                }

                if (
                  graphData.interval === 'minute' &&
                  query.period !== 'realtime'
                ) {
                  return dateFormatter({
                    interval: 'hour',
                    longForm: false,
                    period: query.period
                  })(this.getLabelForValue(val))
                }

                return dateFormatter({
                  interval: graphData.interval,
                  longForm: false,
                  period: query.period,
                  shouldShowYear
                })(this.getLabelForValue(val))
              },
              color:
                theme.mode === UIMode.dark ? 'rgb(161, 161, 170)' : undefined
            }
          }
        },
        interaction: {
          mode: 'index',
          intersect: false
        }
      }
    })
  }

  repositionTooltip(e) {
    const tooltipEl = document.getElementById('chartjs-tooltip-main')
    if (tooltipEl && window.innerWidth >= 768) {
      if (e.clientX > 0.66 * window.innerWidth) {
        tooltipEl.style.right =
          window.innerWidth - e.clientX + window.pageXOffset + 'px'
        tooltipEl.style.left = null
      } else {
        tooltipEl.style.right = null
        tooltipEl.style.left = e.clientX + window.pageXOffset + 'px'
      }
      tooltipEl.style.top = e.clientY + window.pageYOffset + 'px'
      tooltipEl.style.opacity = 1
    }
  }

  componentDidMount() {
    if (this.props.graphData) {
      this.chart = this.regenerateChart()
    }
    window.addEventListener('mousemove', this.repositionTooltip)
  }

  componentDidUpdate(prevProps) {
    const { graphData, theme } = this.props
    const tooltip = document.getElementById('chartjs-tooltip-main')

    if (
      graphData !== prevProps.graphData ||
      theme.mode !== prevProps.theme.mode
    ) {
      if (graphData) {
        if (this.chart) {
          this.chart.destroy()
        }
        this.chart = this.regenerateChart()
        this.chart.update()
      }

      if (tooltip) {
        tooltip.style.display = 'none'
      }
    }

    if (!graphData) {
      if (this.chart) {
        this.chart.destroy()
      }

      if (tooltip) {
        tooltip.style.display = 'none'
      }
    }
  }

  componentWillUnmount() {
    // Ensure that the tooltip doesn't hang around when we are loading more data
    const tooltip = document.getElementById('chartjs-tooltip-main')
    if (tooltip) {
      tooltip.style.opacity = 0
      tooltip.style.display = 'none'
    }
    window.removeEventListener('mousemove', this.repositionTooltip)
  }

  /**
   * The current ticks' limits are set to treat iPad (regular/Mini/Pro) as a regular screen.
   * @param {*} chart - The chart instance.
   * @param {*} dimensions - An object containing the new dimensions *of the chart.*
   */
  updateWindowDimensions(chart, dimensions) {
    chart.options.scales.x.ticks.maxTicksLimit = dimensions.width < 720 ? 5 : 8
  }

  maybeHopToHoveredPeriod(e) {
    const element = this.chart.getElementsAtEventForMode(e, 'index', {
      intersect: false
    })[0]
    const date =
      this.props.graphData.labels[element.index] ||
      this.props.graphData.comparison_labels[element.index]

    if (this.props.graphData.interval === 'month') {
      this.props.navigate({
        search: (search) => ({ ...search, period: 'month', date })
      })
    } else if (this.props.graphData.interval === 'day') {
      this.props.navigate({
        search: (search) => ({ ...search, period: 'day', date })
      })
    }
  }

  render() {
    const { graphData } = this.props
    const canvasClass = classNames('mt-4 select-none', {
      'cursor-pointer': !['minute', 'hour'].includes(graphData?.interval)
    })

    return (
      <FadeIn show={graphData}>
        <div className="relative h-96 print:h-auto print:pb-8 w-full z-0">
          <canvas id="main-graph-canvas" className={canvasClass}></canvas>
        </div>
      </FadeIn>
    )
  }
}

export default function LineGraphWrapped(props) {
  const { query } = useQueryContext()
  const navigate = useAppNavigate()
  const theme = useTheme()
  return (
    <LineGraph {...props} navigate={navigate} query={query} theme={theme} />
  )
}
